package com.tianji.learning.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ByteUtil;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.constants.RedisConstant;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.vo.SignRecordVO;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mapper.SignRecordMapper;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @Author whx
 * @Date 2025/1/11 11:25
 */
@Service
public class SignRecordServiceImpl implements ISignRecordService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitMqHelper mqHelper;

    @Autowired
    private SignRecordMapper signRecordMapper;

    @Override
    public SignResultVO addSignRecords() {
        // 1.签到
        // 1.1.获取登录用户
        Long userId = UserContext.getUser();
        // 1.2.获取日期
        LocalDate now = LocalDate.now();
        // 1.3.拼接key
        String key = RedisConstant.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        // 1.4.计算offset
        int offset = now.getDayOfMonth() - 1;
        // 1.5.保存签到信息
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到！");
        }

        int signDays = CountContinuousDays(key, now.getDayOfMonth());

        int rewardPoints = 0;
        switch (signDays){
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }

        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, rewardPoints + 1)
        );

        // 5.封装返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }

    @Override
    public SignRecordVO getRecords() {
        Long userId = UserContext.getUser();
        // 1.2.获取日期
        LocalDateTime now = DateUtils.now();
        int dayOfMonth = now.getDayOfMonth();

        // 1.3.拼接key返回
        String key =  RedisConstant.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);

        Integer count = signRecordMapper.countByUserId(userId);

        List<Long> result  = redisTemplate.opsForValue().bitField(
                key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)
                ).valueAt(dayOfMonth - count)
        );

        List<Integer> list = new ArrayList<>();

        if(result != null){
            Long num = result.get(0);
            for (int i = 0; i < dayOfMonth ; i++) {
                if((num & 1) == 1){
                    list.add(1);
                }else {
                    list.add(0);
                }
                num >>>= 1;
            }
        }

        int continuousDays = CountContinuousDays(key, dayOfMonth);
        SignRecordVO signRecordVO = new SignRecordVO();
        signRecordVO.setSignDays(continuousDays);
        signRecordVO.setSignRecords(CollUtil.reverse(list.stream().map(ByteUtil::intToByte).collect(Collectors.toList())));

        return signRecordVO;
    }

    /**
     * count continuous days
     * @param key
     * @param dayOfMonth
     * @return
     */
    private int CountContinuousDays(String key, int dayOfMonth) {

        // get bitmap from redis by key
        // this result is a long type value and only one value
        List<Long> result = redisTemplate.opsForValue().bitField(
                key, BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                        .valueAt(0)
        );

        if(CollUtil.isEmpty(result)){
            return 0;
        }

        // get the only one value in 0
        Long num = result.get(0);

        // init a counter
        int count = 0;

        // just judge how many times the counter is that means continuous sign days, when num == 0, return
        while ((num & 1) == 1){
            count++;
            // 无符号右移操作
            num >>>= 1;
        }

        return count;
    }


}
